#!/usr/bin/perl -w
#

# TechTag_poller.pl
#
# Simple ISO host inventory polling for the FEIG readers.
#
#  Copyright (C) 2001-2004 Brian Rudy (brudy@praecogito.com)
# 
#  This program is free software; you can redistribute it and/or modify
#  it under the terms of the GNU General Public License as published by
#  the Free Software Foundation; either version 2 of the License, or
#  (at your option) any later version.
# 
#  This program is distributed in the hope that it will be useful,
#  but WITHOUT ANY WARRANTY; without even the implied warranty of
#  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
#  GNU General Public License for more details.
#
#
# If a tag is found, send a request to the application server
# for an image grab for the detected tags.
#
# 0.01 7/9/2004
# First working version. Only supports polling mode.
#

#use lib "/home/troy/Desktop/bikeswipe";
#print "\@INC is @INC\n";
#use RFID::FEIG::ISC;
use LWP::Simple;


my $DEBUG = 1;

my %status = (
		0x00 => "OK",
		0x01 => "No Transponder",
		0x02 => "Data False \(bad CRC\)",
		0x03 => "Write-Error",
		0x04 => "Address-Error",
		0x05 => "Wrong Transponder-type",
		0x10 => "EEPROM-failure",
		0x11 => "Parameter-Range-Error",
		0x80 => "Unknown Command",
		0x81 => "Length-Error",
		0x82 => "Command not available",
		0x83 => "RF communication error",
		0x94 => "More Data",
		0x95 => "ISO 15693 Error",
             );

my %ISO_error = (
		0x01 => "The command is not supported",
		0x02 => "The command is not recognized",
		0x03 => "The option is not supported",
		0x0F => "Unknown error",
		0x10 => "The specified block is not available",
		0x11 => "The specified block is already locked",
		0x12 => "the specified is locked and its content cannot be changed",
		0x13 => "The specified block was not successfully programmed",
		0x14 => "The specified block was not successfully locked",
		);

my %command = (
		'baud rate detection'		=>	0x52,
		'CPU reset'			=>	0x63,
		'get software version'		=>	0x65,
		'RF reset'			=>	0x69,
		'RF ON_OFF'			=>	0x6A,
		'set output'			=>	0x71,
		'read configuration'		=>	0x80,
		'write configuration'		=>	0x81,
		'save configuration'		=>	0x82,
		'set default configuration'	=>	0x83,
		'ISO15693 command'		=>	0xB0,
	      );

my %ISO15693_command = (
		'Inventory'			=>	0x01,
		'Stay Quiet'			=>	0x02,
		'Lock Multiple Blocks'		=>	0x22,
		'Read Multiple Blocks'		=>	0x23,
		'Write Multiple Blocks'		=>	0x24,
		'Select'			=>	0x25,
		'Reset to Ready'		=>	0x26,
		'Write AFI'			=>	0x27,
		'Lock AFI'			=>	0x28,
		'Write DSFID'			=>	0x29,
		'Lock DSFID'			=>	0x2A,
		'Get System Information'	=>	0x2B,
		'Get Multiple Block Security Status'	=>	0x2C,
		'Read Config Block'		=>	0xA0,
		'Write Config Block'		=>	0xA1,
                       );

# Init the serial port
sub init_port {
  my ($port) = @_;
  open(ISC,"+<$port") || die "opening $port for input: $!";
  system("stty 19200 clocal cread -crtscts -echo -parodd parenb cs8 -cstopb raw < $port");
}

# Shutdown the serial port
sub shutdown_port {
  close(ISC);
}

# Converts between the value and the name of the command for debugging
sub find_command_name {
  my ($command_val) = @_;
  for my $i (keys %command) {
    if ($command{$i} == $command_val) {
      return $i;
    }
  }
}

sub parse_response {
  my (@response_raw) = @_;
  if ($response_raw[0] eq '') {
    if ($DEBUG) {print "No response received!\n";}
    return "0";
  }
  my @response;
  for my $i (0 .. $#response_raw) {
    $response[$i] = unpack('C',$response_raw[$i]);
  }
  print @response;
  my $response_crc_lsb = pop @response;
  my $response_crc_msb = pop @response;
  my ($crc_msb,$crc_lsb) = crc16(@response);
  unless (($crc_msb == $response_crc_msb) && ($crc_lsb == $response_crc_lsb)) {
    if ($DEBUG) {print "CRC error!\n";}
    if ($DEBUG) {printf("Response MSB=%x, expected MSB=%x\n",$response_crc_msb, $crc_msb);}
    if ($DEBUG) {printf("Response LSB=%x, expected LSB=%x\n",$response_crc_lsb, $crc_lsb);}
    return;
  }
  if ($DEBUG > 1) {printf("Address=%x\n",$response[1]);}
  if ($DEBUG > 1) {print "Command=" . find_command_name($response[2]) . "\n";}
  if ($DEBUG > 1) {print "Status=" . $status{$response[3]} ."\n" ;}
  if ($#response > 3) {
    if ($DEBUG > 1) {print "Protocol=";}
    for my $i (4 .. $#response) {
      if ($DEBUG > 1) {printf("%.2x ",$response[$i]);}
    }
    if ($DEBUG > 1) {print "\n";}
  }

  if ((find_command_name($response[2]) eq 'ISO15693 command') && ($status{$response[3]} ne 'No Transponder')) {
    # parse the transponder(s) ID
    my @IDs;
    my $numtags = $response[4];
    for my $i (1 .. ($numtags)) {
      my $tr_type = $response[5+(($i-1)*10)];
      my $DSFID = $response[6+(($i-1)*10)];
      my $ID = "";
      for my $j (0 .. 7) {
        $ID .= sprintf("%.2X",$response[7+$j+(($i-1)*10)]);
      } 
      $IDs[$i-1] = $ID;
      if ($DEBUG > 1) {print "Type=$tr_type, DSFID=$DSFID, ID=$ID\n";}
    }
    return @IDs;
  }
  else {
    return "0";
  }
}


# Detect baud rate of all devices
sub Baud_Rate_Detection {
  my @data;
  $data[0]=0x06;
  $data[1]=0xFF;
  $data[2]=$command{'baud rate detection'};  
  $data[3]=0x00;

  my ($crc_msb,$crc_lsb) = crc16(@data);
  $data[4]=$crc_msb;
  $data[5]=$crc_lsb;
  sendMsg(@data);
  print @data;
}


# Get a list of all the transponders in range
sub ISO_Host_Command_Inventory {
  my @data;
  $data[0]=0x07;
  $data[1]=0xFF;
  $data[2]=$command{'ISO15693 command'};
  $data[3]=$ISO15693_command{'Inventory'};  
  $data[4]=0x00;
         
  my ($crc_msb,$crc_lsb) = crc16(@data);
  $data[5]=$crc_msb;
  $data[6]=$crc_lsb;
  sendMsg(@data);
}

# Read characters on the serial port
# If specified, the given timeout value is used to wait for a response
sub getMsg {
  my $timeout = shift || 0.3;  # set it to something sensible if not specified
  #print "Timeout = $timeout\n";
  my @inbuff = ("");
  my $char;
  my $rin = "";
  vec($rin,fileno(ISC),1) = 1;
  my $i = 0;
  my $rout;
  while (select($rout=$rin, undef, undef, $timeout)) {
    $char = '';
    sysread ISC,$char,1;
    #print "Got:\'" . sprintf("%x",unpack('C',$char)) . "\'\n";
    $inbuff[$i] = $char;
    $i++;
    if (unpack('C',$inbuff[0]) == $i) {
      if ($DEBUG > 1) {print "Done reading $i bytes\n";}
      last;
    }
  }
  if ($inbuff[0] eq '') {
    if ($DEBUG) {print "Error! Timed out waiting for input.\n";}
  }
  return @inbuff;
}


# send some characters.
sub sendMsg {
  my (@msg) = @_;
  for my $i (0 .. $#msg) {
    syswrite ISC, pack('C',$msg[$i]), 1;
  }
}

# Do the funky CRC16 that the FEIG readers use
sub crc16 {
  my (@data) = @_;
  my $CRC_POLYNOM = 0x8408;
  my $CRC_PRESET = 0xFFFF;
  my $crc = $CRC_PRESET;
  for my $i (0 .. $#data) {
    $crc ^= $data[$i];
    my $j;
    for ($j = 0; $j < 8; $j++) {
      if ($crc & 0x0001) {
        $crc = ($crc >> 1) ^ $CRC_POLYNOM;
      }
      else {
        $crc = ($crc >> 1);
      }
    }
  }

  #return $crc;

  # MSB and LSB bytes appear to be bassackwards, go figure
  #my $crc_msb = ($crc >> 8) & 0x00FF;
  #my $crc_lsb = $crc & 0x00FF;
  my $crc_lsb = ($crc >> 8) & 0x00FF;
  my $crc_msb = $crc & 0x00FF;
  #printf ("CRC16=%x, MSB byte=%x, LSB byte=%x\n", $crc, $crc_msb, $crc_lsb);

  return $crc_msb, $crc_lsb;
}

my $serial_port = "/dev/ttyS0";
my $application_server = "zazaconsole";
#my $application_server = "zazaconsole.exhibits.thetech.org";


init_port($serial_port);
Baud_Rate_Detection();
parse_response(getMsg(0.6));

# Main loop
select(undef, undef, undef, 3.00);   # sleep for 3 s
while (1) {

  #select(undef, undef, undef, 0.30);   # sleep for 300ms
  select(undef, undef, undef, 0.03);   # sleep for 30ms

  ISO_Host_Command_Inventory();
  my @IDs = parse_response(getMsg(0.6));  # Scans can take up to 600ms!
  if ((defined $IDs[0]) && ($IDs[0] ne "0")) {
    if (scalar @IDs > 1) {
      my $ID_string = join "&ID=", @IDs;
      print "Requesting single image grab for visitors $ID_string\n";
      if (is_error(getprint("http://" . $application_server . "/cgi-bin/SmartMuseum?operation=SnapImage&ID=" . $ID_string))) {
        print "SnapImage request to $application_server for ID=$ID_string failed!\n";
      }
    }
    else {
      print "Requesting image grab for ID=" . $IDs[0] . "\n";
      if (is_error(getprint("http://" . $application_server . "/cgi-bin/SmartMuseum?operation=SnapImage&ID=" . $IDs[0]))) {
        print "SnapImage request to $application_server for " . $IDs[0] . " failed!\n";
      }      
    }
  }
  else {
    #print "No tags found\n";
  }

}

shutdown_port();

